/** \file
 * \author Pedro Jimenez
 * \date   Wed Nov  5 11:31:12 2008
 * 
 * \brief  Contiene el main y los parámeteros que se pueden indicar por linea de comandos.
 * 
 */

#define _MAIN_CPP_

#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <pthread.h>
#include "../pic_code/PICdata.h"

#include "interface.h"
#include "callbacks.h"
#include "support.h"
#include "serialport.h"

#include "frame_header.hh"
#include "interface_capture.h"
#include "captura.h"
#include "socket.h"
#include "other_functions.h"
#include "options.h"

#include "shutter.h"



camera_control cameras[MAX_CAMERAS]; /**< Contiene la información referente a las dos camaras  */
captura_params_t captura[MAX_CAMERAS]; /**< contiene todos los parámetros necesarios para los hilos que controlan las cámaras */
socket_params_t *socket_params; /**< Datos para comunicación entre varios ordenadores para iniciar o parar automáticamente la grabación */
pthread_t idHiloCapture[2];	/**< hilos de captura de las cámaras */
pthread_t idHiloShow; /**< Hilo de visualización */
pthread_t idHiloPrint;		/**< Hilo para imprimir la info de pantalla */


void onmouse_enfoque (int event, int x, int y, int flags, void* param);



int main (int argc, char *argv[])
{
    //GtkWidget *window1;
    unsigned char reg_tx;
    int i=1;
    GtkWidget * button;
    const char * port;
    
    /// Imprescindible iniciar los párametros de línea de comandos antes de intentar leer las opciones de configuración
    if (!leer_opciones(argc, argv, INI_FILE))
	return 0;

    if (ini_opt("next") >= 1)
    {
	contador_files( ini_opts("nextfile"), ini_opt("next")-1);
    }

    p_Serial_Params = &serial_params;

#ifdef ENABLE_NLS
    bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
    bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
    textdomain (GETTEXT_PACKAGE);
#endif


    p_Serial_Params->pport = INVALID_HANDLE_VALUE;
    if (ini_opt("useserialport"))    {
	for (i=0; i<2; i++)
	{
	    port = ini_opts("serialport", i);
	    empezar_lectura_serialport(p_Serial_Params, port);
	    if (p_Serial_Params->pport != INVALID_HANDLE_VALUE)
		break;
	    printf("No se ha conectado el puerto Serie %s.\n", port);
	}

	if (p_Serial_Params->pport == INVALID_HANDLE_VALUE)
	{
	    printf("\n\nERROR *************************************\n"
		   "\n No se ha conectado el puerto Serie.\n\n");
	    if (ini_opt("q"))
		return -2;
	}

	set_dato_reenviar(p_Serial_Params, 0, ini_opt("retx"));
	//indica la funcion de llamada al recibir datos
	p_Serial_Params->callback_rx = datos_recibido;
	p_Serial_Params->params = NULL;

	//reset de la placa del sincronismo
	reg_tx = PICCAB_MODE | PICCAB_HARD_RESET;
	//reg_tx = PICCAB_MODE | PICCAB_EXT_SYNC_ON;
	Write_Port_buffer(p_Serial_Params, &reg_tx, 1, false);
	//comprueba si existe el puerto serie
	usleep(100000);
	if (!GetHayDatosRecibidos(p_Serial_Params))
	{
	    printf("\nERROR *********************************");
	    printf("Error del puerto Serie: No se ha recibido ningún dato:\n");
	    printf("\n\n\tEsta conectado el puerto serie?\n\n");
	    terminar_lectura_serialport(p_Serial_Params);
	    if (ini_opt("q"))
		return -1;
	}	
    
	printf("Serial port connected on %s\n\n", port);
    }

    /*************
    / bloqueo del compresor
    / *********/
    {
	char lock_str[1024];
	strcpy(lock_str, getenv("HOME"));
	strcat(lock_str, "/captura.lock");

	FILE *fd = fopen(lock_str, "w");
	if(fd){
	    fprintf(fd, "%d", getpid());
	    fclose(fd);
	}
    }

    /**********************************************
    / Inicialización del entorno
    ********************************************/
    stereo_flag = STEREO_OFF;
    external_sync[0] = SYNC_EXTERNAL_OFF;
    external_sync[1] = SYNC_EXTERNAL_OFF;
    esta_grabando = false;
    offset_flag = false;
    grabar_flag = false;
    iniciado_grabar = false;

    light_intensity_value = ini_opt("light");
    gain_value = ini_opt("gain");
    shutter_v = ini_opt("shutter");
    brightness_value = ini_opt("brightness");

    ptimer_left_image = 0;
    page_name = PAGE_CAPTURE;
    maximize_window = false;
    hold_window = false;


    tx_dato_intensidad(light_intensity_value/2);

    //inicializa las cámaras
    int numCams=0;
    CvRect iniroi = {-1, -1, ini_opt("width"), ini_opt("height")};
    numCams = f_capture_init(cameras, iniroi, argc, argv);
    idHiloCapture[0] = 0;
    idHiloCapture[1] = 0;
    if (numCams == 0)
    {
	printf("\n\nERROR *************************************\n\nNo se ha conectado la cámara.\n\n");
	if (!ini_opt("q"))
	{
	    cameras[0].numCams=2;
	    for (i =0; i<cameras[0].numCams; i++)
		cameras[i].image = cvCreateImage(cvSize(cameras[i].width, cameras[i].height ), 
						 IPL_DEPTH_8U, 1);
	    printf("Thread_Create( Thread_Func_F_Simulate ...);\n");
	    pthread_create(&idHiloCapture[0], NULL, pthread_func_f_simulate, (void*)cameras);
	}
	else
	{
	    terminar_lectura_serialport(p_Serial_Params);
	    return -3;
	}
    }

    cameras[0].shutter_control = ini_opt("asc");
    cameras[1].shutter_control = cameras[0].shutter_control;
    captura[0].camera = &cameras[0];
    captura[1].camera = &cameras[1];
    //inicializa la compresion y salvado de imágenes
    init_captura_and_save(captura, ini_opt("buffers"));

    /*
     * The following code was added by Glade to create one of each component
     * (except popup menus), just so that you see something after building
     * the project. Delete any components that you don't want shown initially.
     */
    g_thread_init(NULL);
    gdk_threads_init();

    gtk_set_locale();
    argc=1;
    gtk_init (&argc, &argv);
    /* init threads */

    //inicia el hilo de visualización
    for (i=0; i<cameras[0].numCams; i++)
    {
	cvNamedWindow(SHOW_WINDOW[i], 1);
    	//cvMoveWindow(SHOW_WINDOW[i], 800, 450 + i*300);
    	cvMoveWindow(SHOW_WINDOW[i], 800*i, 450);
    	cvSetMouseCallback(SHOW_WINDOW[i], onmouse);
    }

    printf("Thread_Create( Thread_Func_F_Show_Capture ...);\n");
    pthread_create (&idHiloShow, NULL, pthread_func_f_show_capture , (void*)cameras);

    
    //lanza el hilo del socket para saber cuando está grabando el otro pc
    if (ini_opt("master"))
	socket_params = init_master_socket(ini_opts("ipaddress"), ini_opt("ipport")); 
    else if (ini_opt("slave"))
	socket_params = init_slave_socket(ini_opt("ipport"));


    /***************************
    ainicialización del entorno grafico
    *******************************/
    gdk_threads_enter();
    window1 = create_window1 ();
    gtk_widget_show (window1);

    if (socket_params)
    {
	socket_params->callback = socket_callback;
	socket_params->callback_params = (void*)window1;
    }
    p_Serial_Params->params = window1;

    // Showed the Ficosa and Alcalá logo
    //g_timeout_add (40, f_show_cabintec_logo, NULL);
    //g_timeout_add (40, f_show_alcala_logo,  NULL);

    //desabilita el boton de parar grabación, pues no está grabando ahora
    button = lookup_widget(window1, "button_stop");
    gtk_widget_set_sensitive(button, false);
    gdk_threads_leave();
    
    // para que le de tiempo a inicializar el resto de hilos antes de mostrar la ventana de enfoque
    sched_yield();

    if (ini_opt("focus"))
    {
	int xsize = 475*2;
	int ysize = 50*2;
	gdk_threads_enter();
	//ventana para enfocar la cámara
	cvNamedWindow("Enfoque", 1);
	cvMoveWindow("Enfoque", 50,150);

	IplImage *Enfoque;
	CvFont font1, font2;
	Enfoque = cvCreateImage(cvSize(xsize, ysize), IPL_DEPTH_8U, 3);
	cvSet(Enfoque, CV_RGB(200, 200, 200));
	cvInitFont(&font1, CV_FONT_HERSHEY_COMPLEX, 1.7, 1.7, 0.1, 2, CV_AA );
	cvInitFont(&font2, CV_FONT_HERSHEY_COMPLEX, 0.7, 0.7, 0, 1, CV_AA );
	
	cvPutText(Enfoque, "Haga click sobre esta ventana para", 
		  cvPoint(300,27), &font2, CV_RGB(0,0,0));
	if (numCams == 1)
	    cvPutText(Enfoque, "Enfocar ahora la camara", 
		      cvPoint(100,ysize-15), &font1, CV_RGB(0,0,0));
	else
	    cvPutText(Enfoque, "Enfocar ahora las camaras", 
		      cvPoint(10,ysize-12), &font1, CV_RGB(0,0,0));
	cvShowImage("Enfoque", Enfoque);
	gdk_threads_leave();
	cvReleaseImage(&Enfoque);
	cvSetMouseCallback("Enfoque", onmouse_enfoque);
    }


    if (ini_opt("camsync"))
    {
	//cambia la cámara a modo de sincronismo con la placa del PIC
	button = lookup_widget(window1, "checkbutton_cam_sync");
	gtk_signal_emit_by_name(GTK_OBJECT(button), "clicked");
    }

    if (ini_opt("extsync"))
    {
	//cambia la cámara a modo de sincronismo externo
	button = lookup_widget(window1, "checkbutton_external_sync");
	gtk_signal_emit_by_name(GTK_OBJECT(button), "clicked");
    }

    if (ini_opt("sim"))
    {
	//cambia la cámara a modo de sincronismo externo
	button = lookup_widget(window1, "checkbutton_sim_sync");
	gtk_signal_emit_by_name(GTK_OBJECT(button), "clicked");
    }

    //mira si hay que iniciar la grabación
    if (ini_opt("rec"))
    {
	//cambia la cámara a modo de sincronismo externo
	button = lookup_widget(window1, "button_record");
	gtk_signal_emit_by_name(GTK_OBJECT(button), "clicked");
    }

    //inicia la lectura de la cámara
    for (i=0; i<numCams; i++)
    {
	printf("Thread_Create( Thread_Func_F_Capture %d...);\n", i);
	pthread_create (&idHiloCapture[i], NULL, pthread_func_f_capture , (void*)&captura[i]);

	if (getuid() == 0)     //SCHED_OTHER, SCHED_FIFO, or SCHED_RR
	    change_sched(idHiloCapture[i], SCHED_FIFO, 15);
    }

    pthread_create(&idHiloPrint, NULL, pthread_func_print_capture_info, (void*)captura);

    //cvNamedWindow("Viola & Jones");
    //pthread_t thrVJ;
    //pthread_create(&thrVJ, NULL, pthread_func_lucasycande, &captura[1]);

    gtk_main ();

    return 0;
}


int change_sched(pthread_t thread, int policy, int priority)
{
    int error;
    struct sched_param    param;
    memset(&param, 0, sizeof(param));
    param.sched_priority = priority;
    //SCHED_OTHER, SCHED_FIFO, or SCHED_RR
    if (0 != (error = pthread_setschedparam(thread, policy, &param)))
	//if (0 != (error = pthread_setschedprio(thread, param.sched_priority)))
    {
	printf("Error al cambiar la prioridad %d\n", error);
	perror("");
    }

    int rc;

    if ((rc = pthread_getschedparam(thread, &policy, &param)) != 0)
	printf("Error al leer la prioridad\n");
    else
	printf("policy %d / priority = %d\n", policy, param.sched_priority);
    return param.sched_priority;
}




/**
 * Callback que se produce cuando un evento del mouse afecta a la ventana que pide el enfoque
 * Se encarga de eliminar la ventana de "Enfoque" en caso de hacer click sobre ella
 * 
 * @param event información sobre el botón pulsado en el mouse
 * @param x información sobre la posición del mouse
 * @param y información sobre la posición del mouse
 * @param flags información sobre el estado del mouse
 * @param param No recibe extra parámetros
 */
void onmouse_enfoque (int event, int x, int y, int flags, void* param)
{
    //printf("evento %2d/%2d en %3d,%3d\n", event, flags, x, y);
    //fflush(stdout);

    if (event == 1 or event == 2 )
    {
	onmouse(7, 1, 1, 0, NULL);
    }

}


/**
 * Esta extructura contiene los parámeteros que se pueden pasar o modificar por linea de comandos.
 * 
 */
struct option2 mainopts2[]= {
    //control del programa
    {{"q",0,NULL, true}, true, "",
     "El programa NO sigue adelante si faltan las cámara o la placa de sincronismo no está conectada al puerto serie."},
    {{"nq",0,NULL, OPT_NEG_OTHER}, -1, "",
     "El programa sigue adelante aunque falten las cámara o la placa de sincronismo."},
    {{"rec",0,NULL, true}, false, "",
     ""},
    {{"norec",0,NULL, OPT_NEG_OTHER}, -1, "",
     "Indica si empezar a grabar al iniciar el programa."}, 

    //control de la cámara e imagen de captura
    {{"width", 1, NULL, OPT_VAL_IS_INT}, 1392, "", 
     ""}, 
    {{"height", 1, NULL, OPT_VAL_IS_INT}, 720,  "",
     "Ancho y Alto de la imagen de captura de la cámara" },
    {{"x", 1, NULL, OPT_VAL_IS_INT}, 0,  "",
     ""},
    {{"y", 1, NULL, OPT_VAL_IS_INT}, 160, "",
     "Pixel/offset de inicio en X e Y de la imagen de captura de la cámara. (primer pixel el 0)" },
    {{"light", 1, NULL, OPT_VAL_IS_INT}, 15, "",
     "Valor inicial de intensidad luminosa infrarroja"}, 
    {{"gain", 1, NULL, OPT_VAL_IS_INT}, 200, "",
     "Valor inicial de ganancia, en unidades de la cámara"}, 
    {{"shutter", 1, NULL, OPT_VAL_IS_INT}, 600, "",
     "Valor inicial de tiempo de disparo, en unidades de la cámara"}, 
    {{"brightness", 1, NULL, OPT_VAL_IS_INT}, 0, "",
     "Valor inicial de brillo, en unidades de la cámara"},
    {{"asc", 0, NULL, true}, false, "",
     "Activa el control automático del tiempo de exposición"}, 

    //control del modo de sincronismo
    {{"sim", 0, NULL, true}, false, "",
     ""}, 
    {{"nosim", 0, NULL, OPT_NEG_OTHER}, -1, "",
     "Activa/desactiva la simulación de la señal de sincronismo externa"}, 
    {{"extsync", 0, NULL, true}, false, "",
     ""}, 
    {{"noextsync", 0, NULL, OPT_NEG_OTHER}, -1, "",
     "Habilita/deshabilita el inicio de las cámaras en modo sincronismo externo"
     " Esta opción deshabilita --camsync"}, 
    {{"camsync", 0, NULL, true}, true,  "",
     ""}, 
    {{"nocamsync", 0, NULL, OPT_NEG_OTHER}, -1,  "",
     "Habilita/deshabilita el inicio de las cámaras en modo sincronismo interno"
     " esta opción es sobreescrita por --extsync"},

    //control del video de grabación
    {{"videoname", 1, NULL, OPT_VAL_IS_CHAR}, 0, "video",  
     "Nombre con el que comenzarán los videos a grabar \"videoname_n_fecha_hora.raw\"" },
    {{"videopath", 1, NULL, OPT_VAL_IS_CHAR}, 0, "", 
     "" },
    {{"videopath2", 1, NULL, OPT_VAL_IS_CHAR}, 0, "", 
     "Directorio donde guardar los videos de la primera y segunda cámara" },
    {{"raw", 0, NULL, true}, true, "",
     ""},
    {{"compression", 0, NULL, OPT_NEG_OTHER}, -1, "",
     "Los videos se almacenan en formato RAW o en formato comprimido Lossless"
     ", con el codec FFV1 de las fmpeg"},
    {{"buffers", 1, NULL, OPT_VAL_IS_INT}, 200, "",
     "Numero de buffers a usar para la escritura en disco de los videos (por cada cámara)"},
    {{"next", 1, NULL, OPT_VAL_IS_INT}, -1, "", 
     "Indica el número del siguiente video a usar. (Si next=0, todos los videos a grabar tendrán número 0 (se sobreescribirán, y no se modifica el fichero nextfile). Si se omite o =-1, se lee el siguiente valor del fichero indicado por --nextfile"},
    {{"nextfile", 1, NULL, OPT_VAL_IS_CHAR}, 0,"./videos/next_video.txt", 
     "Fichero que contiene el número del siguiente video a grabar si --next=-1. Si se indica la opcion"
     " --next con valor mayor que 0, el valor indicado se escribirá en el fichero nextfile"},

    //puerto serie
    {{"useserialport", 0, NULL, true}, true, "", 
     ""}, 
    {{"noserialport", 0, NULL, OPT_NEG_OTHER}, -1, "", 
     "No usa un puerto serie para comunicarse con la placa de sincronismo"},
    {{"serialport", 1, NULL, OPT_VAL_IS_CHAR}, 0, "/dev/ttyUSB0",
     ""},
    {{"altserialport", 1, NULL, OPT_VAL_IS_CHAR}, 0, "/dev/ttyS0", 
     "Indica el primer dispositivo a usar para el puerto serie, y un dispositivo alternativo" },
    {{"retx", 1, NULL, OPT_VAL_IS_INT}, 3, "", 
     "Indica el intervalo de tiempo con el que se enviarán los datos al pic."
     " (debe ser menor de 4 segundos)"},

    //activar grabación desde otro PC
    {{"slave", 0, NULL, true}, false, "", 
     ""},
    {{"noslave", 0, NULL, OPT_NEG_OTHER},-1, "",  
     "Indica si el programa queda a la escucha de que otro programa le diga el empiece/final"
     " de grabación"},
    {{"master", 0, NULL, true}, false, "", 
     ""},
    {{"nomaster", 0, NULL, OPT_NEG_OTHER}, -1, "", 
     "Indica si el programa indicará a otro pc, con --ipaddress IP, el empiece/final"
     " de grabación"},
    {{"ipaddress", 1, NULL, OPT_VAL_IS_CHAR}, 0, "127.0.0.1", 
     ""},
    {{"ipport", 1, NULL, OPT_VAL_IS_INT}, 3000, "", 
     "Derección IP y puerto del equipo al que indicar el inicio y final de la grabación"}, 

    //otras opciones
    {{"focus", 0, NULL, true}, false, "", 
     "Muestra una ventana que pide el enfoque manual de las cámaras"}, 

    //obligatorias
    {{"options", 0, NULL, true}, false, "", 
     "Imprime por pantalla las opciones de configuracion del programa"},
    {{"h", 0, NULL, true}, false, "", 
     ""},
    {{"help", 0, NULL, true}, false, "", 
     "Muestra las opciones que se pueden indicar por línea de comandos"},
    {{NULL, 0, NULL, 0}, 0, "", 
     ""}
};

/** 
 * Información de ayuda que se muestra en pantalla con la opción --help 
 */
char main_help[] = "Interfaz_captura:\n" \
    "Captura de video de cámaras Firewire, y grabación al disco.\n\n"	\
    "El programa lee primero las opciones contenidas en el archivo " INI_FILE "\n"\
    "y luego aplica las opciones introducidas por linea de comandos.\n"\
    "Estas últimas por lo tanto sobreescriber a las indicadas en el archivo.\n" \
    "En el arhcivo, cada línea contendrá un parametro de la forma:\n" \
    "--param[ value]\n"\
    "Exactamente igual que los parametros en la linea de comandos.\n" \
    "\nSi un parametro requiere algún valor (indicado con =OPT) y no se indica,\n se generará un error.\n" \
    "\nParámetros:\n\n";
